package cm.tl.pms.util;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.net.NetworkInterface;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Date;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Token
 *
 * @author yangzhenyu
 * @version 1.0.0
 * @since 2017年11月17日 下午2:59:12
 */
public class Token implements Comparable<Token>, Serializable {

    private static final long serialVersionUID = -1833792829731007110L;

    static final Logger logger = LoggerFactory.getLogger(Token.class);

    private static final int LOW_ORDER_THREE_BYTES = 0x00ffffff;

    private static final int MACHINE_IDENTIFIER;
    private static final short PROCESS_IDENTIFIER;
    private static final AtomicInteger NEXT_COUNTER = new AtomicInteger(new SecureRandom().nextInt());

    private static final char[] HEX_CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
            'c', 'd', 'e', 'f'};

    private final int timestamp;
    private final int machineIdentifier;
    private final short processIdentifier;
    private final int counter;

    static {
        try {
            MACHINE_IDENTIFIER = createMachineIdentifier();
            PROCESS_IDENTIFIER = createProcessIdentifier();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 或得一个新的token
     *
     * @return the new id
     */
    public static Token get() {
        return new Token();
    }

    public static String getId() {
        return new Token().toString();
    }

    /**
     * 检查是否符合tokenId 标准
     *
     * @param hexString 一个token 字符串
     * @return hexString 是否符合 token 标准
     */
    public static boolean isValid(final String hexString) {
        if (hexString == null) {
            throw new IllegalArgumentException();
        }

        int len = hexString.length();
        if (len != 24) {
            return false;
        }

        for (int i = 0; i < len; i++) {
            char c = hexString.charAt(i);
            if (c >= '0' && c <= '9') {
                continue;
            }
            if (c >= 'a' && c <= 'f') {
                continue;
            }
            if (c >= 'A' && c <= 'F') {
                continue;
            }

            return false;
        }

        return true;
    }

    /**
     * @return 获取int类型的机器标识符
     */
    public static int getGeneratedMachineIdentifier() {
        return MACHINE_IDENTIFIER;
    }

    /**
     * @return 获取int类型的进程标识符
     */
    public static int getGeneratedProcessIdentifier() {
        return PROCESS_IDENTIFIER;
    }

    /**
     * currentCounter 自动递增计数器的当前值
     *
     * @return 当前递增计数器的值
     */
    public static int getCurrentCounter() {
        return NEXT_COUNTER.get();
    }

    /**
     * @param time    时间
     * @param machine 机器标识符
     * @param inc     当前增量计数器的值
     * @return 通过参数返回一个token
     */
    public static Token createFromLegacyFormat(final int time, final int machine, final int inc) {
        return new Token(time, machine, inc);
    }

    /**
     * Create a new object id.
     */
    public Token() {
        this(new Date());
    }

    /**
     * Constructs a new instance using the given date.
     *
     * @param date the date
     */
    public Token(final Date date) {
        this(dateToTimestampSeconds(date), MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, NEXT_COUNTER.getAndIncrement(),
                false);
    }

    /**
     * @param date    the date
     * @param counter the counter
     */
    public Token(final Date date, final int counter) {
        this(date, MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, counter);
    }

    /**
     * @param date              date
     * @param machineIdentifier 机器标示符
     * @param processIdentifier 进程标示符
     * @param counter           增量值
     */
    public Token(final Date date, final int machineIdentifier, final short processIdentifier, final int counter) {
        this(dateToTimestampSeconds(date), machineIdentifier, processIdentifier, counter);
    }

    /**
     * @param timestamp         时间秒
     * @param machineIdentifier 机器标示符
     * @param processIdentifier 进程标示符
     * @param counter           当前计数值
     */
    public Token(final int timestamp, final int machineIdentifier, final short processIdentifier, final int counter) {
        this(timestamp, machineIdentifier, processIdentifier, counter, true);
    }

    private Token(final int timestamp, final int machineIdentifier, final short processIdentifier, final int counter,
                  final boolean checkCounter) {
        if ((machineIdentifier & 0xff000000) != 0) {
            throw new IllegalArgumentException(
                    "The machine identifier must be between 0 and 16777215 (it must fit in three bytes).");
        }
        if (checkCounter && ((counter & 0xff000000) != 0)) {
            throw new IllegalArgumentException(
                    "The counter must be between 0 and 16777215 (it must fit in three bytes).");
        }
        this.timestamp = timestamp;
        this.machineIdentifier = machineIdentifier;
        this.processIdentifier = processIdentifier;
        this.counter = counter & LOW_ORDER_THREE_BYTES;
    }

    /**
     * 用一个24字节的十六进制字符串初始化一个token实例
     *
     * @param hexString 需要转换的字符串
     */
    public Token(final String hexString) {
        this(parseHexString(hexString));
    }

    /**
     * 用12位字节数组初始化一个token实例
     *
     * @param bytes 字节数组
     */
    public Token(final byte[] bytes) {
        if (bytes == null) {
            throw new IllegalArgumentException();
        }
        if (bytes.length != 12) {
            throw new IllegalArgumentException("need 12 bytes");
        }

        timestamp = makeInt(bytes[0], bytes[1], bytes[2], bytes[3]);
        machineIdentifier = makeInt((byte) 0, bytes[4], bytes[5], bytes[6]);
        processIdentifier = (short) makeInt((byte) 0, (byte) 0, bytes[7], bytes[8]);
        counter = makeInt((byte) 0, bytes[9], bytes[10], bytes[11]);
    }

    Token(final int timestamp, final int machineAndProcessIdentifier, final int counter) {
        this(legacyToBytes(timestamp, machineAndProcessIdentifier, counter));
    }

    /**
     * @param timestamp                   时间
     * @param machineAndProcessIdentifier 机器和进程标识
     * @param counter                     增量
     * @return 整编过后的字节数组，符合token标准
     */
    private static byte[] legacyToBytes(final int timestamp, final int machineAndProcessIdentifier, final int counter) {
        byte[] bytes = new byte[12];
        bytes[0] = int3(timestamp);
        bytes[1] = int2(timestamp);
        bytes[2] = int1(timestamp);
        bytes[3] = int0(timestamp);
        bytes[4] = int3(machineAndProcessIdentifier);
        bytes[5] = int2(machineAndProcessIdentifier);
        bytes[6] = int1(machineAndProcessIdentifier);
        bytes[7] = int0(machineAndProcessIdentifier);
        bytes[8] = int3(counter);
        bytes[9] = int2(counter);
        bytes[10] = int1(counter);
        bytes[11] = int0(counter);
        return bytes;
    }

    /**
     * 转换为字节数组，要注意生成字节数组的顺序
     *
     * @return the byte array
     */
    public byte[] toByteArray() {
        byte[] bytes = new byte[12];
        bytes[0] = int3(timestamp);
        bytes[1] = int2(timestamp);
        bytes[2] = int1(timestamp);
        bytes[3] = int0(timestamp);
        bytes[4] = int2(machineIdentifier);
        bytes[5] = int1(machineIdentifier);
        bytes[6] = int0(machineIdentifier);
        bytes[7] = short1(processIdentifier);
        bytes[8] = short0(processIdentifier);
        bytes[9] = int2(counter);
        bytes[10] = int1(counter);
        bytes[11] = int0(counter);
        return bytes;
    }

    /**
     * @return the timestamp
     */
    public int getTimestamp() {
        return timestamp;
    }

    /**
     * @return 机器标示符
     */
    public int getMachineIdentifier() {
        return machineIdentifier;
    }

    /**
     * @return 进程标示符
     */
    public short getProcessIdentifier() {
        return processIdentifier;
    }

    /**
     * @return the counter
     */
    public int getCounter() {
        return counter;
    }

    /**
     * @return the Date
     */
    public Date getDate() {
        return new Date(timestamp * 1000L);
    }

    /**
     * 24字节的十六进制字符串表示。
     *
     * @return a string representation of the token in hexadecimal format
     */
    public String toHexString() {

        char[] chars = new char[24];
        int i = 0;
        for (byte b : toByteArray()) {
            chars[i++] = HEX_CHARS[b >> 4 & 0xF];
            chars[i++] = HEX_CHARS[b & 0xF];
        }
        return new String(chars);

        // final StringBuilder buf = new StringBuilder(24);
        // for (final byte b : toByteArray()) {
        // buf.append(String.format("%02x", b & 0xff));
        // }
        // return buf.toString();

    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        Token token = (Token) o;

        if (counter != token.counter) {
            return false;
        }
        if (machineIdentifier != token.machineIdentifier) {
            return false;
        }
        if (processIdentifier != token.processIdentifier) {
            return false;
        }
        if (timestamp != token.timestamp) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int result = timestamp;
        result = 31 * result + machineIdentifier;
        result = 31 * result + (int) processIdentifier;
        result = 31 * result + counter;
        return result;
    }

    @Override
    public int compareTo(final Token other) {
        if (other == null) {
            throw new NullPointerException();
        }

        byte[] byteArray = toByteArray();
        byte[] otherByteArray = other.toByteArray();
        for (int i = 0; i < 12; i++) {
            if (byteArray[i] != otherByteArray[i]) {
                return ((byteArray[i] & 0xff) < (otherByteArray[i] & 0xff)) ? -1 : 1;
            }
        }
        return 0;
    }

    @Override
    public String toString() {
        return toHexString();
    }

    private static int createMachineIdentifier() {
        // build a 2-byte machine piece based on NICs info
        int machinePiece;
        try {
            StringBuilder sb = new StringBuilder();
            Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
            while (e.hasMoreElements()) {
                NetworkInterface ni = e.nextElement();
                sb.append(ni.toString());
                byte[] mac = ni.getHardwareAddress();
                if (mac != null) {
                    ByteBuffer bb = ByteBuffer.wrap(mac);
                    try {
                        sb.append(bb.getChar());
                        sb.append(bb.getChar());
                        sb.append(bb.getChar());
                    } catch (BufferUnderflowException shortHardwareAddressException) { // NOPMD
                        // mac with less than 6 bytes. continue
                    }
                }
            }
            machinePiece = sb.toString().hashCode();
        } catch (Throwable t) {
            // exception sometimes happens with IBM JVM, use random
            machinePiece = (new SecureRandom().nextInt());
            logger.warn("Failed to get machine identifier from network interface, using random number instead", t);
        }
        machinePiece = machinePiece & LOW_ORDER_THREE_BYTES;
        return machinePiece;
    }

    /**
     * 产生进程标示符。并不需要每个类加载。可以由 NEXT_COUNTER 保证唯一性
     *
     * @return 进程标示符
     */
    private static short createProcessIdentifier() {
        short processId;
        try {
            String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
            if (processName.contains("@")) {
                processId = (short) Integer.parseInt(processName.substring(0, processName.indexOf('@')));
            } else {
                processId = (short) java.lang.management.ManagementFactory.getRuntimeMXBean().getName().hashCode();
            }

        } catch (Throwable t) {
            processId = (short) new SecureRandom().nextInt();
            logger.warn("Failed to get process identifier from JMX, using random number instead", t);
        }

        return processId;
    }

    private static byte[] parseHexString(final String s) {
        if (!isValid(s)) {
            throw new IllegalArgumentException("invalid hexadecimal representation of an ObjectId: [" + s + "]");
        }

        byte[] b = new byte[12];
        for (int i = 0; i < b.length; i++) {
            b[i] = (byte) Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16);
        }
        return b;
    }

    private static int dateToTimestampSeconds(final Date time) {
        return (int) (time.getTime() / 1000);
    }

    // 通过四个字节组装成int类型
    private static int makeInt(final byte b3, final byte b2, final byte b1, final byte b0) {
        // CHECKSTYLE:OFF
        return (((b3) << 24) | ((b2 & 0xff) << 16) | ((b1 & 0xff) << 8) | ((b0 & 0xff)));
        // CHECKSTYLE:ON
    }

    private static byte int3(final int x) {
        return (byte) (x >> 24);
    }

    private static byte int2(final int x) {
        return (byte) (x >> 16);
    }

    private static byte int1(final int x) {
        return (byte) (x >> 8);
    }

    private static byte int0(final int x) {
        return (byte) (x);
    }

    private static byte short1(final short x) {
        return (byte) (x >> 8);
    }

    private static byte short0(final short x) {
        return (byte) (x);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            String id = new Token().toHexString();
            System.out.println(id);
        }
        Token id = new Token();
        System.out.println(id.getCounter());
        System.out.println(id.getMachineIdentifier());
        System.out.println(id.getTimestamp());
        System.out.println(id.getDate());

        byte[] bs = {86, -98, 24, -18, 0, 4, 10, -128, 0, 106, 89, 88};
        System.out.println(new Token(bs));

        System.out.println(new Token((int) new Date().getTime() / 1000, 11111145, 22222228));

        System.out.println(new Token(1, 1, 1));

        System.out.println(new Token());
        System.out.println(id.toString());
        System.out.println(id.toHexString());
    }

}
